﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using WorkflowWizart.Lib.Entities;
using WorkflowWizart.Lib.Entities.WorkflowEntities;
using WorkflowWizart.Lib.Utility.Costants;
using WorkflowWizart.Lib.Utility.ExtensionMethods;
using WorkflowWizart.Lib.Utility.Log;

namespace WorkflowWizart.Lib.Engine
{
    public class WorkflowEngine
    {
        public static object RunWorkFlowItem(WorkflowItem item)
        {
            object result = null;
            if (!string.IsNullOrEmpty(item.OperationCommandId))
            {
                result = OperationCommandEngine.RunCommand(item.OperationCommandId, GetParametersvalue(item.ParentWorkflow, item.Parameters));
            }
            else if (!string.IsNullOrEmpty(item.WorkflowId))
            {
                result = OperationCommandEngine.RunCommand(item.WorkflowId, GetParametersvalue(item.ParentWorkflow, item.Parameters));
            }
            else
            {
                if (item.Parameters != null && item.Parameters.Count > 0)
                {
                    var parameterValues = GetParametersvalue(item.ParentWorkflow, item.Parameters);
                    result = parameterValues[0];
                }
                else
                { 
                    result = null;
                }
            }
            item.Result = result;
            return result;
        }

        public static object RunWorkflow(Workflow workflow)
        {
            int counter = 0;
            Stopwatch workflowTimeCalculator = new Stopwatch();
            workflowTimeCalculator.Start();

            for (int i = 0; i < workflow.Items.Count; i++)
            {
                Stopwatch currentWorkflowItemTimeCalculator = new Stopwatch();
                currentWorkflowItemTimeCalculator.Start();

                if (workflow.Items[i] is ReturnWorkFlowItem)
                {
                    workflowTimeCalculator.Stop();
                    LogHelper.AddWorkflowLog(workflow.Id, workflowTimeCalculator.ElapsedTicks);

                    return GetParametersvalue(workflow, workflow.Items[i].Parameters)[0];
                }
                else if (workflow.Items[i] is LoopWorkFlowItem)
                {
                    var loop = workflow.Items[i] as LoopWorkFlowItem;
                    bool conditionResult = true;
                    for (int c = 0; c < loop.Conditions.Count; c++)
                    {
                        conditionResult = WorkflowEngine.RunWorkFlowItem(loop.Conditions[c]).ToBool();
                        if (!conditionResult)
                        {
                            break;
                        }
                    }
                    loop.Result = conditionResult;
                    if (!conditionResult)
                    {
                        ///finds the end of loop item index (finds the index of END LOOP)
                        i = loop.ChildItems.Max(c => c.Index) + 1;
                        continue;
                    }
                    for (int c = 0; c < loop.ChildItems.Count; c++)
                    {
                        WorkflowEngine.RunWorkFlowItem(loop.ChildItems[c]);
                    }

                    counter++;
                    if (counter > 5000)
                    {
                        currentWorkflowItemTimeCalculator.Stop();
                        LogHelper.AddWorkflowLoopItemOverflow(workflow.Id, loop.Id, currentWorkflowItemTimeCalculator.ElapsedTicks, counter);
                        continue;
                    }

                    i = loop.Index - 1;
                }
                else if (workflow.Items[i] is ConditionWorkFlowItem)
                {
                    var condition = workflow.Items[i] as ConditionWorkFlowItem;
                    bool conditionResult = true;
                    for (int c = 0; c < condition.Conditions.Count; c++)
                    {
                        conditionResult = WorkflowEngine.RunWorkFlowItem(condition.Conditions[c]).ToBool();
                        if (!conditionResult)
                        {
                            break;
                        }
                    }
                    condition.Result = conditionResult;
                    if (!conditionResult)
                    {
                        ///finds the end of loop item index (finds the index of END LOOP)
                        i = condition.ChildItems.Max(c => c.Index) + 1;
                        continue;
                    }
                    for (int c = 0; c < condition.ChildItems.Count; c++)
                    {
                        WorkflowEngine.RunWorkFlowItem(condition.ChildItems[c]);
                    }
                }
                else if (workflow.Items[i].ParentItem == null)
                {
                    RunWorkFlowItem(workflow.Items[i]);
                }

                currentWorkflowItemTimeCalculator.Stop();
            }

            workflowTimeCalculator.Stop();
            if (workflow.ReturnType == ReturnTypes.Void)
            {
                LogHelper.AddWorkflowLog(workflow.Id, workflowTimeCalculator.ElapsedTicks);
            }

            else
            {
                LogHelper.AddWorkflowWarningLog(workflow.Id, workflowTimeCalculator.ElapsedTicks, "Couldn't find return item in workflow");
            }

            return null;
        }

        public static object[] GetParametersvalue(Workflow wf, List<WorkflowItemParameter> parameters)
        {

            object[] parametersResult = new object[parameters.Count];
            for (int i = 0; i < parameters.Count; i++)
            {
                if (!string.IsNullOrEmpty(parameters[i].Id))
                {

                    var parameterValueByParameter = wf.Parameters.SingleOrDefault(w => w.Id == parameters[i].Id);
                    if (parameterValueByParameter == null)
                    {
                        var parameterValueByScope = wf.ScopesVariable.SingleOrDefault(w => w.Id == parameters[i].Id);
                        if (parameterValueByScope == null)
                        {
                            var parameterValueByResults = wf.Items.SingleOrDefault(w => w.Id + ":Result" == parameters[i].Id);
                            if (parameterValueByResults == null)
                            {
                                parametersResult[i] = parameters[i].Value;
                            }
                            else
                            {
                                parametersResult[i] = parameterValueByResults.Result;
                            }
                        }
                        else
                        {
                            parametersResult[i] = parameterValueByScope.Value;
                        }
                    }
                    else
                    {
                        parametersResult[i] = parameterValueByParameter.Value;
                    }
                }
                else
                {
                    parametersResult[i] = parameters[i].Value;
                }
            }
            return parametersResult;
        }
    }
}
